《Android 基础(三十五)》 RecyclerView多类型Item的正确实现姿势

简介

RecyclerView是我们开发过程中经常使用到的一个元素,原生的RecyclerView.Adapter基本上可以满足一般的需求,关于RecyclerView的基础介绍请移步:

RecyclerView基础使用

关于多类型的Item,原生的Adapter可以通过getItemViewType返回对应的ViewHolder类型,然后在onCreateViewHolder传入的type参数,生成不同的ViewHolder,更要命的是数据绑定过程中onBindViewHolder只传入ViewHolder对象和position,针对不同的ViewHolder,我们只能通过instanceof和强制类型装换来对不同的ViewHolder进行不同的数据操作。

这样做的弊端就是,后续如果需要添加一个类型,几乎需要修改Adapter类中的每一个方法,不利于维护。

类结构

这里写图片描述

目的

  1. 便于维护。增删Item的类型不需要修改Adapter的代码;
  2. 条例清晰。不同类型的Item对应不同的Bean类,对应不用的ViewHolder,对应不同的layout, item type <-> layout <—> ViewHolder <-> java bean
  3. 工厂接口。抽象出TypeFactory接口,针对同一个Bean,可以使用不同的layout和不同的ViewHolder。

思路

  1. 所有Bean类实现统一Visitable接口,实现type方法,返回对应的layout id;
  2. 创建BaseViewHolder使用泛型,T为Bean类。使用SparseArray存储View,View的id作为唯一标识。
  3. 具体的ViewHolder类,继承BaseViewHolder,传入不同的Bean类,方便不同的ViewHolder中绑定不同的数据。
  4. Adapter实现中使用Visitable List同一处理列表数据,使用事项TypeFactory接口的实例返回ItemType,viewHolder等

代码结构

这里写图片描述

代码内容

Visitable接口

1
2
3
public interface Visitable {
int type(TypeFactory typeFactory);
}

TypeFactory接口

1
2
3
4
5
6
7
public interface TypeFactory {
int type(BannerBean bannerBean);
int type(ContentBean contentBean);
int type(SearchBean searchBean);
int type(DividerBean dividerBean);
BaseViewHolder createViewHolder(int type, View itemView);
}

BaseViewHolder抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract  class BaseViewHolder<T> extends RecyclerView.ViewHolder{
SparseArray<View> mViews;
View mItemView;

public BaseViewHolder(View itemView) {
super(itemView);
mItemView = itemView;
mViews = new SparseArray<>();
}

public View getView(int resId) {
View view = mViews.get(resId);

if(view== null) {
view = mItemView.findViewById(resId);
mViews.put(resId, view);
}
return view;
}

public abstract void bindViewData(T data);
}

TypeFactory实现类: ItemTypeFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ItemTypeFactory implements TypeFactory {
public static final int BANNER_ITEM_LAYOUT = R.layout.rv_item_banner;
public static final int CONTENT_ITEM_LAYOUT = R.layout.rv_item_content;
public static final int SEARCH_ITEM_LAYOUT = R.layout.rv_item_search;
public static final int DIVIDER_ITEM_LAYOUT = R.layout.rv_item_divider;

@Override
public int type(BannerBean bannerBean) {
return BANNER_ITEM_LAYOUT;
}

@Override
public int type(ContentBean contentBean) {
return CONTENT_ITEM_LAYOUT;
}

@Override
public int type(DividerBean dividerBean){
return DIVIDER_ITEM_LAYOUT;
}

@Override
public int type(SearchBean dividerBean){
return SEARCH_ITEM_LAYOUT;
}

@Override
public BaseViewHolder createViewHolder(int type, View itemView) {
switch (type) {
case BANNER_ITEM_LAYOUT:
return new BannerViewHolder(itemView);
case SEARCH_ITEM_LAYOUT:
return new SearchViewHolder(itemView);
case CONTENT_ITEM_LAYOUT:
return new ContentViewHolder(itemView);
case DIVIDER_ITEM_LAYOUT:
return new DividerViewHolder(itemView);
default:
return null;
}
}
}

各种Java Bean类

BannerBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BannerBean implements Visitable {
int[] mResIds;

public BannerBean(int[] mResIds) {
this.mResIds = mResIds;
}

public int[] getmResIds() {
return mResIds;
}

public void setmResIds(int[] mResIds) {
this.mResIds = mResIds;
}

@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="200dp">

<cn.bingoogolapple.bgabanner.BGABanner
android:id="@+id/bgabanner_header"
android:layout_width="match_parent"
android:layout_height="200dp">
</cn.bingoogolapple.bgabanner.BGABanner>

</LinearLayout>

ContentBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ContentBean implements Visitable{
String content;

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public ContentBean(String content) {

this.content = content;
}

@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_content"
android:textStyle="italic"
android:fontFamily="sans-serif-condensed"
android:textSize="16sp"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

DividerBean

1
2
3
4
5
6
7
public class DividerBean implements Visitable{

@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/divider">

</LinearLayout>

SearchBean

1
2
3
4
5
6
public class SearchBean implements Visitable {
@Override
public int type(TypeFactory typeFactory) {
return typeFactory.type(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="50dp"
android:padding="6dp">

<EditText
android:layout_marginLeft="30dp"
android:id="@+id/et_search"
android:layout_width="0dp"
android:layout_weight="9"
android:maxLines="1"
android:textSize="10sp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:hint="输入文字搜索..."
android:background="@drawable/bg_search"
android:layout_height="match_parent" />

<ImageView
android:id="@+id/bt_search"
android:layout_width="0dp"
android:layout_weight="2"
android:src="@drawable/search"
android:layout_height="match_parent" />
</LinearLayout>

MultiRecyclerAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MultiRecyclerAdapter extends RecyclerView.Adapter<BaseViewHolder> {
List<Visitable> mData;
TypeFactory typeFactory;

public MultiRecyclerAdapter(List<Visitable> mData) {
this.mData = mData;
this.typeFactory = new ItemTypeFactory();
}

@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
return typeFactory.createViewHolder(viewType, view);
}

@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.bindViewData(mData.get(position));
}

@Override
public int getItemViewType(int position) {
return mData.get(position).type(typeFactory);
}

@Override
public int getItemCount() {
return (mData != null ? mData.size() : 0);
}
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class MultiActivity extends AppCompatActivity {

@BindView(R.id.rv_content)
RecyclerView rvContent;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
ButterKnife.bind(this);


List<Visitable> beans = new ArrayList<>();
int[] resIds = {R.mipmap.banner_one, R.mipmap.banner_two, R.mipmap.banner_three, R.mipmap.banner_four, R.mipmap.banner_five};
beans.add(new BannerBean(resIds));
beans.add(new SearchBean());
beans.add(new DividerBean());
beans.add(new ContentBean("one"));
beans.add(new ContentBean("one"));
beans.add(new ContentBean("one"));
beans.add(new DividerBean());
beans.add(new ContentBean("two"));
beans.add(new ContentBean("two"));
beans.add(new ContentBean("two"));
beans.add(new DividerBean());
beans.add(new ContentBean("three"));
beans.add(new ContentBean("three"));
beans.add(new ContentBean("three"));
beans.add(new DividerBean());
beans.add(new ContentBean("four"));
beans.add(new ContentBean("four"));
beans.add(new ContentBean("four"));
beans.add(new DividerBean());


MultiRecyclerAdapter multiRecyclerAdapter = new MultiRecyclerAdapter(beans);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

rvContent.setAdapter(multiRecyclerAdapter);
rvContent.setLayoutManager(linearLayoutManager);

}
}

效果

这里写图片描述

写在最后

思路来源:
https://medium.com/@dpreussler/writing-better-adapters-1b09758407d2

github上也有很多开源的万能Adapter,但是个人感觉这个思路虽然实现上代码会相对较多,但是层次分析,便于维护,扩展性较好。想要添加一个新类型的ItemType,只需要稍作修改即可快速完成,不用修改Adapter的代码。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×